import path from 'path';
import { nanoid } from 'nanoid';
import { errorDialog } from '@e/dialog';
import { __path } from '@e/methods/path';
import { BrowserWindow } from 'electron';
import { ColorScheme } from '@type/utils';
import { readMdFile } from './readMdFile';
import { getImageType } from '@e/methods/image';
import { downloadFile } from '@e/methods/download';
import { generateImageResource } from './resource';
import { writeResource } from '@e/methods/resource';
import { ReplaceOptions, ReplaceResult } from '@type/replace';
import { MdFileInfo, ImageReferenceType } from '@type/main-windowObject';
import { resourceToZipFile, resourceTo7zFile } from '@e/methods/compress';
import { downloadImages, generateImageDownloadInfo } from './downloadImages';
import { writeFile, writeNewFile, moveFile, copyFile } from '@e/methods/file';
import { replaceImageReferencePath, queryReplaceImageReferencePath } from '@e/methods/replace';
import { FileExistsOperation, FileOperationResult, FileOperationsType_1 } from '@type/file';
import {
	Mode1Data,
	Mode2Data,
	Mode3Data,
	ImageMoveConfig,
	ImagePathType,
	Mode4Data,
	Mode6Data,
} from '@type/main-modeData';
import {
	WindowServiceEvent,
	WindowServiceEventListener,
	ImageCacheData,
	WindowServiceState,
	MessageAcceptType,
	MessageType,
	MessageObject,
	currentWorkMode,
	ImageCacheDataValue,
	ImageDirInfo,
	ParseFileOperationsConfigResult,
	FileOperationsConfig,
	innerFileOperationsResult,
	Resource,
} from '@type/WindowService';

/**
 * 窗口服务对象
 */
export class WindowService {
	constructor(win: BrowserWindow, id: number, filePath?: string, state?: Object) {
		this.window = win;
		this.id = id;
		if (filePath) this.filePath = filePath;
		if (state) Object.assign(this.state, state);
	}

	/**
	 * 窗口对象
	 */
	readonly window: BrowserWindow;

	/**
	 * 窗口id
	 */
	readonly id: number;

	/**
	 * 文件路径
	 */
	filePath?: string;

	/**
	 * 文件信息
	 */
	fileInfo?: MdFileInfo;

	/**
	 * 当前消息 id
	 */
	messageID = -1;

	/**
	 * 图片缓存数据
	 */
	readonly imageCacheData: ImageCacheData = new Map();

	/**
	 * 状态
	 */
	readonly state: WindowServiceState = {
		colorScheme: 'dark',
		work: {
			ongoing: false,
			currentMode: null,
		},
	};

	/**
	 * 颜色
	 */
	readonly colors = {
		// 暗色的
		dark: {
			backgroundColor: '#262727',
		},
		// 明亮的
		light: {
			backgroundColor: '#ededed',
		},
	};

	/**
	 * 事件列表
	 */
	readonly event: WindowServiceEvent = {
		load: [],
	};

	/**
	 * 添加事件
	 */
	on(type: keyof WindowServiceEvent, listener: WindowServiceEventListener, once: boolean = false) {
		let id = nanoid();

		// 防止id重复
		while (this.event[type].some((t) => t.id === id)) {
			id = nanoid();
		}

		this.event[type].push({
			id,
			listener,
			once,
		});

		return id;
	}

	/**
	 * 移除事件
	 */
	off(type: keyof WindowServiceEvent, p: WindowServiceEventListener | string) {
		const index = this.event[type].findIndex((t) => t.listener === p || t.id === p);
		if (index !== -1) {
			this.event[type].splice(index, 1);
		}
	}

	// 重载
	send(channel: 'work-completed'): void;
	send(channel: 'message', type: MessageAcceptType, obj: MessageObject): void;
	send(channel: 'set-fileInfo', data: [MdFileInfo, number]): void;
	// send(channel: string, ...args: any[]): void;

	/**
	 * 向渲染进程发送数据
	 */
	send(channel: string, ...args: any[]) {
		this.window.webContents.send(channel, ...args);
	}

	// 重载
	sendMessage(type1: 'insert', type2: MessageType, message: string): number;
	sendMessage(type1: 'update', type2: MessageType, message: string, id: number): void;

	/**
	 * 发送消息方法
	 */
	sendMessage(type1: MessageAcceptType, type2: MessageType, message: string, id?: number) {
		let myId = type1 === 'insert' ? ++this.messageID : (id as number);

		// 消息对象
		const messageObject: MessageObject = {
			id: myId,
			type: type2,
			message,
		};

		this.send('message', type1, messageObject);

		if (type1 === 'insert') {
			// 保存一下id
			return myId;
		}
	}

	/**
	 * 关闭当前文档
	 */
	closeCurrentDocument() {
		this.filePath = undefined;
		this.fileInfo = undefined;
	}

	/**
	 * 设置颜色方案
	 */
	setColorScheme(colorScheme: ColorScheme) {
		this.state.colorScheme = colorScheme;

		// 根据新的颜色方案设置窗口颜色
		if (colorScheme === 'dark') {
			this.window.setBackgroundColor(this.colors.dark.backgroundColor);
		} else {
			this.window.setBackgroundColor(this.colors.light.backgroundColor);
		}
	}

	/**
	 * 设置工作状态
	 * @param ongoing 是否处于工作中
	 * @param mode 当前工作模式
	 */
	setWorkingState(ongoing: boolean, mode: currentWorkMode) {
		this.state.work.ongoing = ongoing;
		this.state.work.currentMode = mode;
	}

	/**
	 * 工作完成
	 * @param shouldSendMessage 是否需要发送消息
	 * @param rereadDocumentInfo 是否需要重新读取文档信息
	 * @param workSuccess 工作是否成功
	 */
	workCompleted(shouldSendMessage = false, rereadDocumentInfo = true, workSuccess = true) {
		// 设置工作状态
		this.setWorkingState(false, null);
		// 向渲染进程发送工作完成消息
		this.send('work-completed');

		if (workSuccess) {
			this.sendMessage('insert', 'success', '操作完成');
		} else {
			this.sendMessage('insert', 'error', '操作失败');
		}

		if (rereadDocumentInfo) {
			// 重新读取文档
			this.getDocumentInfo(undefined, shouldSendMessage).then((arr) => {
				this.send('set-fileInfo', arr);
			});
		}
	}

	/**
	 * 获取文档信息
	 * @param shouldSendMessage 是否需要发送消息
	 */
	getDocumentInfo(_path?: string, shouldSendMessage = true): Promise<[MdFileInfo, number]> {
		if (_path) this.filePath = _path;

		return new Promise((resolve, reject) => {
			let messageId = -1;

			if (shouldSendMessage) {
				messageId = this.sendMessage('insert', 'info', `正在读取文件 "${this.filePath}"`);
			}

			readMdFile(this.filePath as string).then(
				(data) => {
					this.fileInfo = data;
					resolve([data, messageId]);
				},
				(e) => {
					reject();
					errorDialog('文件读取失败', e.message + `\npath: "${_path}"`);
				}
			);
		});
	}

	/**
	 * 从网络加载图片
	 */
	async loadImageFromNetwork(url: string) {
		// 判断是否在图片缓存中
		if (this.imageCacheData.has(url)) {
			return this.imageCacheData.get(url);
		}

		try {
			// 下载图片
			const buf = await downloadFile(url);
			// 获取图片类型
			const res = getImageType(buf);

			if (res) {
				const obj: ImageCacheDataValue = {
					buffer: buf,
					base64URL: `data:${res.mime};base64,${buf.toString('base64')}`,
					...res,
				};

				// 添加到图片缓存
				this.imageCacheData.set(url, obj);

				return obj;
			}
		} catch (error) {
			return void 0;
		}
	}

	/**
	 * 获取图片的目录信息
	 */
	getImageDirInfo(): ImageDirInfo[] {
		if (!this.fileInfo) return [];

		return this.fileInfo.imageInfo.reduce((t, item) => {
			// 找到所有本地图片
			if (item.type === 'relative' || item.type === 'absolute') {
				let filePath = item.absolute as string;

				const result = __path.check.file(filePath);
				// 检查图片，且图片信息不是重复的
				if (result === 'ok' && !item.exist) {
					let fileDir = path.dirname(filePath);

					const i = t.find((s) => s.dirPaht === fileDir);

					if (i) {
						i.images.push(item);
					} else {
						t.push({
							dirPaht: fileDir,
							images: [item],
						});
					}
				}
			}

			return t;
		}, [] as ImageDirInfo[]);
	}

	/**
	 * 查询本地图片引用路径替换信息
	 */
	queryImageReferencePathReplaceInfo(
		arr: ReplaceOptions,
		neededType: ImageReferenceType[] = ['absolute', 'relative']
	) {
		const content = this.fileInfo?.content || '';
		const imageInfo = this.fileInfo?.imageInfo || [];

		return queryReplaceImageReferencePath(content, arr, imageInfo, neededType);
	}

	/**
	 * 发送替换结果消息
	 */
	private sendReplaceResultMessage(replaceResult: ReplaceResult) {
		// 统计替换成功/失败的数量
		const [successCount, errorCount] = replaceResult.results.reduce(
			(t, item) => {
				if (item.success) {
					t[0]++;
				} else {
					t[1]++;
				}

				return t;
			},
			[0, 0]
		);

		this.sendMessage('insert', 'info', `替换完成：成功(${successCount}) 失败(${errorCount})`);
	}

	/**
	 * 替换图片引用路径(内部)
	 */
	private innerReplaceImageReferencePath(options: ReplaceOptions, flags: string = 'g', strict: boolean = false) {
		const replaceResult = replaceImageReferencePath(this.fileInfo?.content || '', options, flags, strict);

		this.sendReplaceResultMessage(replaceResult);

		return replaceResult;
	}

	/**
	 * 写入文件(内部)
	 */
	private async innerWriteFile(value: string, filePath = this.filePath as string) {
		// 写入文件
		const writeResult = await writeFile(filePath, value);

		if (writeResult.success) {
			this.sendMessage('insert', 'success', `文件写入成功：${this.filePath}`);
		} else {
			this.sendMessage('insert', 'error', `文件写入失败：${writeResult.error}`);
		}
	}

	/**
	 * 文件操作(内部)
	 */
	private async innerFileOperations(configs: FileOperationsConfig[]) {
		const results: innerFileOperationsResult[] = [];
		let fileExistsOperation: FileExistsOperation | undefined;

		for (let i = 0; i < configs.length; i++) {
			const item = configs[i];
			let result: FileOperationResult;
			let modeStr: string;

			if (item.mode === 'move') {
				modeStr = '移动';
				result = await moveFile(this.window, item.target, item.dest, fileExistsOperation);
			} else {
				modeStr = '复制';
				result = await copyFile(this.window, item.target, item.dest, fileExistsOperation);
			}

			// 当用户选择了记住的操作时，保存操作
			if (result.rememberMyChoice) {
				fileExistsOperation = result.fileExistsOperation;
			}

			results.push({
				...result,
				innerType: item.mode,
				id: item.id,
			});

			switch (result.type) {
				case 'error': // 写入发生了错误
					this.sendMessage('insert', 'error', `${modeStr}失败，目标路径：${item.dest}`);
					break;
				case 'cancel':
					this.sendMessage('insert', 'warning', `文件已存在，用户取消了${modeStr}，冲突路径：${item.dest}`);
					break;
			}
		}

		return results;
	}

	/**
	 * 解析文件操作配置
	 */
	private parseFileOperationsConfig(
		arr: ImageMoveConfig[],
		moveType: FileOperationsType_1,
		replaceType: ImagePathType,
		documentDirPath = (this.fileInfo as MdFileInfo).dirPath
	): ParseFileOperationsConfigResult {
		let index = 0;

		return arr.reduce(
			(t, item) => {
				item.children.forEach((item2) => {
					const dest = path.join(item.dirPaht, item2.imageInfo.filename as string);

					t.fileOperationsList.push({
						id: index,
						target: path.join(item2.dirPaht, item2.imageInfo.filename as string),
						dest,
						mode: moveType,
					});

					let substituted = dest;

					// 用户选择了使用原路径类且该图片在文档是以相对路径引用
					const s1 = replaceType === 'original' && item2.imageInfo.type === 'relative';
					// 用户选择了相对路径
					const s2 = replaceType === 'relative';
					// 满足以上条件之一将路径转为相对路径
					if (s1 || s2) {
						substituted = __path.relative(documentDirPath, substituted);
					}

					t.replaceList.push({
						id: index,
						target: item2.imageInfo.target,
						substituted,
						directReplace: true,
					});

					index++;
				});

				return t;
			},
			{ fileOperationsList: [], replaceList: [] } as ParseFileOperationsConfigResult
		);
	}

	/**
	 * 模式1处理函数
	 */
	async mode1Fn(data: Mode1Data) {
		// 防止多次执行
		if (this.state.work.ongoing) return;
		this.setWorkingState(true, 'mode1');

		// 生成下载信息
		const downloadInfo = generateImageDownloadInfo(data, this.fileInfo?.imageInfo || []);
		// 下载图片
		const downloadResults = await downloadImages.call(this, downloadInfo);
		// 根据下载结果，生成替换配置
		const replaceOptions: ReplaceOptions = downloadResults
			.filter((i) => i.result)
			.map((item) => {
				let substituted = path.join(item.savePath, item.filename);

				if (data.pathType === 'relative') {
					substituted = __path.relative((this.fileInfo as MdFileInfo).dirPath, substituted, false, () => {
						this.sendMessage(
							'insert',
							'warning',
							`"${item.filename}" 引用路径没有成功转化为相对路径，图片所在路径：${substituted}`
						);
					});
				}

				return {
					target: item.url,
					substituted,
					directReplace: true,
				};
			});

		// 替换
		const replaceResult = this.innerReplaceImageReferencePath(replaceOptions);

		// 写入文件
		await this.innerWriteFile(replaceResult.content);

		// 调用工作完成方法
		this.workCompleted();
	}

	/**
	 * 模式2处理函数
	 */
	async mode2Fn(data: Mode2Data) {
		if (this.state.work.ongoing) return;
		this.setWorkingState(true, 'mode2');

		// 替换
		const replaceResult = this.innerReplaceImageReferencePath(data, '', true);

		// 写入文件
		await this.innerWriteFile(replaceResult.content);

		// 调用工作完成方法
		this.workCompleted();
	}

	/**
	 * 模式3处理函数
	 */
	async mode3Fn(data: Mode3Data) {
		if (this.state.work.ongoing) return;
		this.setWorkingState(true, 'mode3');

		// 解析图片移动配置
		const parseResults = this.parseFileOperationsConfig(
			data.imageMoveConfig,
			data.imageMovementMethod,
			data.pathType
		);

		// 移动 / 复制图片
		const fileOperationsResults = await this.innerFileOperations(parseResults.fileOperationsList);

		// 去除失败的结果
		const replaceList = parseResults.replaceList.filter((t) => {
			return fileOperationsResults.some((n) => n.id === t.id && n.success);
		});

		// 替换
		const replaceResult = this.innerReplaceImageReferencePath(replaceList);

		// 写入文件
		await this.innerWriteFile(replaceResult.content);

		// 调用工作完成方法
		this.workCompleted();
	}

	/**
	 * 模式4处理函数
	 */
	async mode4Fn(data: Mode4Data) {
		if (this.state.work.ongoing) return;
		this.setWorkingState(true, 'mode4');

		let workSuccess = true;

		// 移动文档
		const [documentMoveResult] = await this.innerFileOperations([
			{
				target: this.filePath as string,
				dest: data.newPath,
				mode: data.documentMoveType,
			},
		]);

		if (documentMoveResult.success) {
			this.filePath = data.newPath;

			// 替换列表
			const replaceList: ReplaceOptions = [];

			if (data.moveImage) {
				// 解析图片移动配置
				const parseResults = this.parseFileOperationsConfig(
					data.imageMoveConfig,
					data.moveImageType,
					data.imagePathType,
					path.dirname(data.newPath)
				);

				// 移动 / 复制图片
				const fileOperationsResults = await this.innerFileOperations(parseResults.fileOperationsList);

				// 去除失败的结果
				parseResults.replaceList.forEach((t) => {
					if (fileOperationsResults.some((n) => n.id === t.id && n.success)) {
						replaceList.push(t);
					}
				});
			} else {
				// 没有开启图片移动的情况
				this.fileInfo?.imageInfo.forEach((item) => {
					// 找到所有的相对路径，并解析出替换列表
					if (item.type === 'relative' && !item.exist) {
						replaceList.push({
							directReplace: true,
							target: item.target,
							substituted: __path.relative(
								path.dirname(data.newPath),
								item.absolute as string,
								false,
								() => {
									this.sendMessage(
										'insert',
										'warning',
										`"${item.filename}" 引用路径没有成功转化为相对路径，图片所在路径：${item.absolute}`
									);
								}
							),
						});
					}
				});
			}

			// 替换
			const replaceResult = this.innerReplaceImageReferencePath(replaceList);

			// 写入文件
			await this.innerWriteFile(replaceResult.content, data.newPath);
		} else {
			workSuccess = false;
		}

		// 调用工作完成方法
		this.workCompleted(false, true, workSuccess);
	}

	/**
	 * 模式6处理函数
	 */
	async mode6Fn(data: Mode6Data) {
		if (this.state.work.ongoing) return;
		this.setWorkingState(true, 'mode6');

		// 前缀
		const prefix = 'build-';
		// 图片保存目录
		const imageSavePath = 'images';
		// 是否成功
		let workSuccess = true;
		// 输出文档的文件名
		let docmuentName = this.fileInfo?.fileName as string;
		// 输出目录名称
		let outputDirName = __path.removeExt(prefix + this.fileInfo?.fileName);
		// 输出文件名
		let outputFileName = __path.removeExt(prefix + this.fileInfo?.fileName);
		// 压缩包的后缀名
		let compressExt = data.compress;

		// 输出为单文件或启用了压缩
		if (data.outputSingleFile || data.compress !== 'none') {
			if (data.compress !== 'none') {
				outputFileName += '.' + compressExt;
			} else {
				outputFileName += '.md';
			}
			
			// 检查文件名在输出目录是否存在
			outputFileName = __path.addNumberFilename(data.outputPath, outputFileName, 'file') || outputFileName;
		} else {			
			// 检查文件夹名称在输出目录是否存在
			outputDirName = __path.addNumberFilename(data.outputPath, outputDirName, 'dir') || outputDirName;
		}

		// 获取图片资源
		const { imageResources, replaceList } = await generateImageResource.call(
			this,
			data.downloadImage,
			imageSavePath,
			data.outputSingleFile
		);

		// 替换路径
		const replaceResult = this.innerReplaceImageReferencePath(replaceList);

		// 组装资源
		let resource: Resource[];
		// 输出为单文件的情况
		if (data.outputSingleFile) {
			// 当输出为单文件且未启用压缩时给文档名加上前缀
			if (data.compress === 'none') docmuentName = prefix + docmuentName;
			resource = [
				{
					name: docmuentName,
					type: 'file',
					data: Buffer.from(replaceResult.content),
				},
			];
		} else {
			// 非单文件的情况
			resource = [
				{
					name: outputDirName,
					type: 'dir',
					children: [
						{
							name: docmuentName,
							type: 'file',
							data: Buffer.from(replaceResult.content),
						},
						{
							name: imageSavePath,
							type: 'dir',
							children: imageResources,
						},
					],
				},
			];
		}

		// 不压缩直接写入资源
		if (data.compress === 'none') {
			await writeResource(data.outputPath, resource, this.window);
		} else if (data.compress === 'zip') {
			// 资源转为 zip
			const { buffer } = resourceToZipFile(resource);

			// 写入 zip 文件
			const { success } = await writeNewFile(this.window, path.join(data.outputPath, outputFileName), buffer);

			workSuccess = success;
		} else if (data.compress === '7z') {
			const { buffer } = await resourceTo7zFile(resource, this.window)

			// 写入 7z 文件
			const { success } = await writeNewFile(this.window, path.join(data.outputPath, outputFileName), buffer);

			workSuccess = success;
		}

		// 调用工作完成方法
		this.workCompleted(false, true, workSuccess);
	}
}
